/*
 * messaging.c
 *
 *  Created on: May 20, 2012
 *      Author: burek
 */

#include "messaging.h"

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#include "logger.h"

// Da li je poruka finalizovana?
int cm_mutable(CMessage * msg)
{
	if(msg == NULL) return 0;
	if(msg->buffer != NULL) return 0;
	if(msg->paramCount >= MAX_PARAMS - 1) return 0;
	return 1;
}

// Da li su prvih 'l' charova cifre?
int cm_checkNum(const char * txt, int l)
{
	int i;
	for(i = 0; i < l; ++i)
		if(txt[i] < '0' || txt[i] > '9')
			return 0;
	return 1;
}

// Ispisi dekodovanu poruku sa svim njenim parametrima
void cm_dumpParams(int id, CMessage * msg)
{
	int i;
	if(msg == NULL) { lprintf(id, "{NULL}"); fflush(stdout); return; }

	lprintf(id, "{Message %03d}", msg->code);
	for(i = 0; i < msg->paramCount; ++i)
	{
		switch(msg->pType[i])
		{
		case CM_INT:
			lprintf(id, "%d] INT     %d", i, msg->pVal[i].ival);
			break;

		case CM_FLOAT:
			lprintf(id, "%d] FLOAT   %.3f", i, msg->pVal[i].fval);
			break;

		case CM_STRING:
			lprintf(id, "%d] STRING  \"%s\"", i, msg->pVal[i].sval);
			break;

		case CM_NONE:
			lprintf(id, "%d] EMPTY", i);
			break;

		default:
			lprintf(id, "%d] ERROR?", i);
			break;
		}
	}
	lprintf(id, "\n");
}

// Napravi novu poruku sa datim kodom
CMessage * cm_createMessage(int code)
{
	//CMessage * msg = (CMessage*)calloc(1, sizeof(CMessage));
	CMessage * msg = (CMessage*)malloc(sizeof(CMessage));
	memset(msg, 0, sizeof(CMessage));
	msg->code = code;
	return msg;
}

// Dekoduj poruku iz sirovih primljenih bajtova
CMessage * cm_decodeMessage(const char * bufferWide, int length)
{
	char numBuffer[9], buffer[4096] = {0};
	CMessage * msg = NULL;
	int id, p = 4, n = 0, i, len = 0, empty = 1;

	// Komunicira se wide-charovima, nazalost
	for(i = 0; i < length / 2; ++i)
		buffer[i] = bufferWide[i * 2];
	buffer[length / 2 + 1] = '\0';
	length /= 2;
	length++;

	if(buffer == 0 || length < 4)	// Mora imati bar trocifren kod i spejs nakon toga
	{
		printf("Message empty or too short\n");
		return NULL;
	}

	if(!cm_checkNum(buffer, 3))
	{
		printf("Message does not begin with 3-digit code\n");
		return NULL;
	}

	if(buffer[3] != ' ')
	{
		printf("Invalid formatting, standard separator not found\n");
		return NULL;
	};

	strncpy(numBuffer, buffer, 3);
	numBuffer[3] = '\0';
	sscanf(numBuffer, "%d", &id);

	msg = (CMessage*)calloc(1, sizeof(CMessage));
	msg->code = id;

	msg->buffer = (char*)malloc(length + 1);
	strcpy(msg->buffer, buffer);
	msg->length = length;

	while(p < length)
	{
		// Pokusaj prepoznavanja tipa parametra, samo sto su mi ostali
		// malo izignorisali specifikaciju...
		empty = 1;
		if(p < length - 8)
		{
			// FLOAT
			if(cm_checkNum(buffer + p + 1, 3) &&
				cm_checkNum(buffer + p + 5, 3) &&
				buffer[p + 4] == '.' &&
				(buffer[p] == '+' || buffer[p] == '-') &&
				(buffer[p + 8] == CM_PARAM_SEPARATOR || buffer[p + 8] == '\0'))
			{
				msg->pType[n] = CM_FLOAT;
				strncpy(numBuffer, buffer + p, 8);
				numBuffer[8] = '\0';
				sscanf(numBuffer, "%f", &(msg->pVal[n].fval));
				n++;
				p += 9;
				empty = 0;
				continue;
			}
		}

		if(empty)
		{
			if(p < length - 5)
			{
				// INT
				if(cm_checkNum(buffer + p, 5) && (buffer[p + 5] == CM_PARAM_SEPARATOR || buffer[p + 5] == '\0'))
				{
					msg->pType[n] = CM_INT;
					strncpy(numBuffer, buffer + p, 5);
					numBuffer[5] = '\0';
					sscanf(numBuffer, "%d", &(msg->pVal[n].ival));
					n++;
					p += 6;
					empty = 0;
					continue;
				}
			}

			if(empty)
			{
				// STRING
				for(i = p; i < length; ++i)
				{
					if(buffer[i] == CM_PARAM_SEPARATOR || buffer[i] == '\0')
						break;
				}

				len = i - p;
				if(len > 0)
				{
					msg->pType[n] = CM_STRING;
					msg->pVal[n].sval = (char*)malloc(len + 1);
					strncpy(msg->pVal[n].sval, buffer + p, len);
					msg->pVal[n].sval[len] = '\0';
					n++;
				}
				p += len + 1;
			}
		}
	}

	msg->paramCount = n;
	return msg;
}

// Dodaj INT u poruku
int cm_appendInt(CMessage * msg, int number)
{
	int i = 0;
	if(!cm_mutable(msg)) return 0;
	i = msg->paramCount++;
	msg->pType[i] = CM_INT;
	msg->pVal[i].ival = number;
	return 1;
}

// Dodaj FLOAT u poruku
int cm_appendFloat(CMessage * msg, float number)
{
	int i = 0;
	if(!cm_mutable(msg)) return 0;
	i = msg->paramCount++;
	msg->pType[i] = CM_FLOAT;
	msg->pVal[i].fval = number;
	return 1;
}

// Dodaj STRING u poruku
int cm_appendString(CMessage * msg, const char * text)
{
	int i = 0, len = 0;
	if(!cm_mutable(msg)) return 0;
	if(text == NULL) return 0;
	len = strlen(text);
	i = msg->paramCount++;
	msg->pType[i] = CM_STRING;
	msg->pVal[i].sval = (char*)malloc(len + 1);
	strcpy(msg->pVal[i].sval, text);
	return 1;
}

// Slozi konacne bajtove od liste parametara
int cm_finalizeMessage(CMessage * msg)
{
	int i, p = 4;
	char *wide;
	if(!cm_mutable(msg)) return 0;

	// Izracunaj duzinu
	msg->length = 4;
	if(msg->paramCount == 0)
	{
		msg->length = 4;
	}
	else
	{
		for(i = 0; i < msg->paramCount; ++i)
		{
			switch(msg->pType[i])
			{
			case CM_INT:
				msg->length += 6;
				break;
			case CM_FLOAT:
				msg->length += 9;
				break;
			case CM_STRING:
				msg->length += strlen(msg->pVal[i].sval) + 1;
				break;
			}
		}
	}

	msg->buffer = (char*)malloc(msg->length);
	sprintf(msg->buffer, "%03d", msg->code);
	msg->buffer[3] = CM_SEPARATOR;

	// Poslozi bajtove, parametar po parametar
	for(i = 0; i < msg->paramCount; ++i)
	{
		switch(msg->pType[i])
		{
		case CM_INT:
			sprintf(msg->buffer + p, "%05d", msg->pVal[i].ival);
			p += 5;
			break;
		case CM_FLOAT:
			sprintf(msg->buffer + p + 3, "%.3f", msg->pVal[i].fval - ((int)msg->pVal[i].fval));
			sprintf(msg->buffer + p, "%+04d", ((int)msg->pVal[i].fval));
			msg->buffer[p + 4] = '.';
			p += 8;
			break;
		case CM_STRING:
			strcpy(msg->buffer + p, msg->pVal[i].sval);
			p += strlen(msg->pVal[i].sval);
			break;
		}

		if(i < msg->paramCount - 1)
		{
			msg->buffer[p] = CM_PARAM_SEPARATOR;
			p++;
		}
	}

	// Dupliraj u wide-charove
	wide = (char*)malloc(msg->length * 2);
	for(i = 0; i < msg->length; ++i)
	{
		wide[i * 2] = msg->buffer[i];
		wide[i * 2 + 1] = '\0';
	}
	free(msg->buffer);
	msg->buffer = wide;
	msg->length *= 2;

	return 1;
}

// Oslobodi memoriju poruke
void cm_destroyMessage(CMessage * msg)
{
	int i;
	if(msg == NULL) return;
	if(msg->buffer) free(msg->buffer);
	for(i = 0; i < MAX_PARAMS; ++i)
		if(msg->pType[i] == CM_STRING)
			free(msg->pVal[i].sval);
	free(msg);
}


